package org.springframework.data.redis.connection.lettuce;

import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.api.StatefulConnection;
import io.lettuce.core.codec.RedisCodec;
import lombok.extern.slf4j.Slf4j;
import org.joor.Reflect;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;

import java.lang.reflect.Constructor;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 扩展的连接池工厂
 *
 * @author wujr
 * 2023/8/8
 * @version 1.0
 * 【变更历史】
 * [wujr 2023/8/8 1.0 新增]
 */
@Slf4j
public class ExtendLettuceConnectionFactory extends LettuceConnectionFactory {
    private final ConcurrentHashMap<RedisCodec<?, ?>, LettuceConnectionProvider> _connectionProvider = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<RedisCodec<?, ?>, LettuceConnectionProvider> _reactiveConnectionProvider = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<RedisCodec<?, ?>, SharedConnection<?>> _connection = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<RedisCodec<?, ?>, SharedConnection<?>> _reactiveConnection = new ConcurrentHashMap<>();

    public ExtendLettuceConnectionFactory(RedisSentinelConfiguration sentinelConfiguration,
                                    LettuceClientConfiguration clientConfig) {
        super(sentinelConfiguration, clientConfig);
    }
    public ExtendLettuceConnectionFactory(RedisClusterConfiguration clusterConfiguration,
                                    LettuceClientConfiguration clientConfig) {
        super(clusterConfiguration, clientConfig);
    }
    public ExtendLettuceConnectionFactory(RedisStandaloneConfiguration standaloneConfig,
                                    LettuceClientConfiguration clientConfig) {
        super(standaloneConfig, clientConfig);
    }
    @SuppressWarnings("rawtypes")
    public StatefulConnection reactiveConnection(RedisCodec<?, ?> codec){
        boolean shareNativeConnection = Reflect.on(this).get("shareNativeConnection");
        if (!shareNativeConnection){
            return null;
        }
        SharedConnection sharedConnection = _reactiveConnection.computeIfAbsent(codec, k -> {
            LettuceConnectionProvider provider = getOrCreateReactiveConnectionProvider(codec);
            return new SharedConnection<>(provider);
        });
        return Objects.requireNonNull(sharedConnection.getConnection());
    }

    protected LettuceConnectionProvider getOrCreateConnectionProvider(RedisCodec<?, ?> codec){
        return _connectionProvider.computeIfAbsent(codec, k -> {
            AbstractRedisClient client = Reflect.on(ExtendLettuceConnectionFactory.this).get("client");
            LettuceConnectionProvider provider = Reflect.on(ExtendLettuceConnectionFactory.this).call("createConnectionProvider", client, codec).get();
            String providerClassName = LettuceConnectionFactory.class.getName() + "$ExceptionTranslatingConnectionProvider";
            Class<?> providerClass;
            Constructor<?> constructor;
            try {
                providerClass = Class.forName(providerClassName);
                constructor = providerClass.getDeclaredConstructor(LettuceConnectionProvider.class);
                constructor.setAccessible(true);
                return (LettuceConnectionProvider)constructor.newInstance(provider);
            }catch (Exception e){
                log.warn("构造redis的连接池提供者异常：", e);
                throw new RuntimeException("构造链接提供者异常");
            }
        });
    }
    protected LettuceConnectionProvider getOrCreateReactiveConnectionProvider(RedisCodec<?, ?> codec){
        return _reactiveConnectionProvider.computeIfAbsent(codec, k -> {
            AbstractRedisClient client = Reflect.on(ExtendLettuceConnectionFactory.this).get("client");
            LettuceConnectionProvider provider = Reflect.on(ExtendLettuceConnectionFactory.this).call("createConnectionProvider", client, codec).get();
            String providerClassName = LettuceConnectionFactory.class.getName() + "$ExceptionTranslatingConnectionProvider";
            Class<?> providerClass;
            Constructor<?> constructor;
            try {
                providerClass = Class.forName(providerClassName);
                constructor = providerClass.getDeclaredConstructor(LettuceConnectionProvider.class);
                constructor.setAccessible(true);
                return (LettuceConnectionProvider)constructor.newInstance(provider);
            }catch (Exception e){
                log.warn("构造redis的连接池提供者异常：", e);
                throw new RuntimeException("构造链接提供者异常");
            }
        });
    }
}
